﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Loki.Utils;

namespace Luna.Connectors.ZohoCRM
{
    public sealed class ZohoEntityCollection<T> : LokiObject, ICollection<T> where T : ZohoEntity
    {
        private readonly Func<T> BUILDER;
        private readonly ICollection<T> COLLECTION;

        public ZohoEntityCollection()
            : this(false)
        {
        }

        public ZohoEntityCollection(bool P_ReadOnly)
            : this(new List<T>(), P_ReadOnly)
        {
        }

        private ZohoEntityCollection(ICollection<T> P_Collection, bool P_ReadOnly)
        {
            var L_EntityName = Attribute.GetCustomAttribute(typeof(T), typeof(ModuleNameAttribute)) as ModuleNameAttribute;
            if (L_EntityName == null)
            {
                throw BuildError<InvalidOperationException>(Resources.Errors.MISSING_MODULE_ATTRIBUTE);
            }

            EntityName = L_EntityName.Name;
            IsReadOnly = P_ReadOnly;
            this.COLLECTION = P_Collection;
            BUILDER = ExpressionHelper.New<T>().Compile();
        }

        public int Count
        {
            get { return COLLECTION.Count; }
        }

        public bool IsReadOnly { get; private set; }

        internal string EntityName { get; private set; }

        public void Add(T P_Item)
        {
            AssertNotReadOnly();
            COLLECTION.Add(P_Item);
        }

        public ZohoEntityCollection<T> AsReadOnly()
        {
            return new ZohoEntityCollection<T>(COLLECTION, true);
        }

        public void Clear()
        {
            AssertNotReadOnly();
            COLLECTION.Clear();
        }

        public bool Contains(T P_Item)
        {
            return COLLECTION.Contains(P_Item);
        }

        public void CopyTo(T[] P_Array, int P_ArrayIndex)
        {
            COLLECTION.CopyTo(P_Array, P_ArrayIndex);
        }

        public IEnumerator<T> GetEnumerator()
        {
            return COLLECTION.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public bool Remove(T P_Item)
        {
            AssertNotReadOnly();
            return COLLECTION.Remove(P_Item);
        }

        public XElement ToXml()
        {
            XElement L_Root = new XElement(EntityName);
            var L_RowNum = 1;
            foreach (var L_Entity in this)
            {
                L_Entity.AppendTo(L_Root, L_RowNum++);
            }

            return L_Root;
        }

        public string ToXmlString()
        {
            var L_Element = ToXml();
            var L_StringBuilder = new StringBuilder();

            using (var L_StringWriter = new StringWriter(L_StringBuilder))
            {
                using (var L_XmlWriter = new XmlTextWriter(L_StringWriter))
                {
                    L_Element.WriteTo(L_XmlWriter);
                }
            }

            return L_StringBuilder.ToString();
        }

        internal void LoadFromXml(XElement P_Parent)
        {
            if (P_Parent.Name != SerializerParams.ELEMENT_NODATA)
            {
                if (P_Parent.Name != EntityName)
                {
                    throw BuildErrorFormat<InvalidOperationException>(Resources.Errors.INVALID_FORMAT_BAD_ENTITY_NAME, P_Parent.Name, EntityName);
                }

                foreach (var L_Element in P_Parent.Elements())
                {
                    if (L_Element.Name == SerializerParams.ELEMENT_ROW)
                    {
                        var L_Entity = BUILDER();
                        L_Entity.LoadFromXml(L_Element);
                        COLLECTION.Add(L_Entity);
                    }
                }
            }
        }

        private void AssertNotReadOnly()
        {
            if (IsReadOnly)
            {
                throw BuildError<InvalidOperationException>(Resources.Errors.ASSERT_COLLECTION_READ_ONLY);
            }
        }
    }
}